package com.wisecoach.gatewayplus.spring.interceptor;

import com.wisecoach.gatewayplus.annotation.ChaincodeTransaction;
import com.wisecoach.gatewayplus.info.GatewayInfo;
import com.wisecoach.gatewayplus.info.GatewayInfoContextHolder;
import com.wisecoach.gatewayplus.info.GatewayInfoProvider;
import com.wisecoach.gatewayplus.session.GatewayContext;
import com.wisecoach.gatewayplus.session.GatewayContextHolder;
import com.wisecoach.gatewayplus.session.GatewaySession;
import com.wisecoach.gatewayplus.session.GatewaySessionProvider;
import com.wisecoach.gatewayplus.transaction.*;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.Ordered;

import javax.annotation.Nonnull;
import java.lang.reflect.Method;

/**
 * 链码事务拦截器
 * {@code @author:} wisecoach
 * {@code @date:} 2023/3/7 上午10:57
 * {@code @version:} 1.0.0
 */
@Slf4j
public class ChaincodeTransactionInterceptor implements MethodInterceptor, Ordered {

    private static final int DEFAULT_PRIORITY = 10;

    private final TransactionAttributeSource txAttrSource;
    private final TransactionAdvice advice;
    private final TransactionContextProvider transactionContextProvider;
    private final GatewayInfoProvider gatewayInfoProvider;
    private final GatewaySessionProvider gatewaySessionProvider;

    public ChaincodeTransactionInterceptor(TransactionAttributeSource txAttrSource, TransactionAdvice advice,
                                            TransactionContextProvider transactionContextProvider,
                                            GatewayInfoProvider gatewayInfoProvider,
                                            GatewaySessionProvider gatewaySessionProvider) {
        this.txAttrSource = txAttrSource;
        this.advice = advice;
        this.transactionContextProvider = transactionContextProvider;
        this.gatewayInfoProvider = gatewayInfoProvider;
        this.gatewaySessionProvider = gatewaySessionProvider;
    }

    /**
     * 大致流程如下：
     * 1. 首先要判断方法是否为 ChaincodeTransaction {@link ChaincodeTransaction}
     * 2. 读取事务的策略 {@link TransactionStrategy}
     * 3. 根据策略生成对应的事务上下文 {@link TransactionContext}，并且将上下文放入到 {@link TransactionContextHolder} 中
     * 4. 取得gatewayInfo，然后生成对应的gateway，将gateway存入到GatewayContextHolder
     * 5. 执行 {@link TransactionAdvice#before(Method, Object[], Object)}
     * 6. 执行被代理加强方法
     * 7. 执行 {@link TransactionAdvice#afterReturning(Object, Method, Object[], Object)}
     * 8. 若期间抛出异常 {@link TransactionAdvice#throwing(Method, Object[], Object, Throwable)}
     * 9. 清空 {@link TransactionContextHolder} 和 {@link GatewayContextHolder}
     * 10. 返回结果
     */
    @Override
    public Object invoke(@Nonnull MethodInvocation invocation) throws Throwable {
        Object obj = invocation.getThis();
        Method method = invocation.getMethod();
        Object[] args = invocation.getArguments();
        // 1
        TransactionAttribute txAttr = txAttrSource.getTransactionAttribute(method, method.getDeclaringClass());
        if (txAttr == null) {
            return invocation.proceed();
        }
        // 3 根据不同策略，判断如何创建新事务
        TransactionContext transactionContext = transactionContextProvider.getTransactionContext(txAttr);
        TransactionContextHolder.setContext(transactionContext);
        if (transactionContext.getTransaction().isNewTransaction()) {
            log.info(String.format("开启一个事务: id=%s", TransactionContextHolder.getContext().getTransaction().getId()));
        }
        try {
            log.debug("开始创建Gateway");
            // 4
            GatewayContext oldGateway = GatewayContextHolder.getContext();
            GatewayInfo gatewayInfo = gatewayInfoProvider.getGatewayInfo(obj, method, args);
            GatewaySession gatewaySession = gatewaySessionProvider.getGatewaySession(gatewayInfo);
            GatewayContext context = GatewayContextHolder.getEmptyContext();
            context.setGateway(gatewaySession);
            context.setOldGateway(oldGateway);
            GatewayContextHolder.setContext(context);
            // 5
            advice.before(method, args, obj);
            // 6
            log.debug("开始调用被加强方法");
            Object ret = invocation.proceed();
            log.debug("被加强方法执行完毕");
            // 7
            advice.afterReturning(ret, method, args, obj);
            log.debug("本地事务提交完毕");
            // 10
            return ret;
        } catch (Throwable t) {
            // 8
            advice.throwing(method, args, obj, t);
            log.debug("本地事务回滚完毕");
            throw t;
        } finally {
            // 9
            if (TransactionContextHolder.getContext() != null) {
                // 如果是最外层的新事务，则可以直接释放
                if (TransactionContextHolder.getContext().getTransaction().isNewTransaction()) {
                    log.info(String.format("完成一个事务: id=%s", TransactionContextHolder.getContext().getTransaction().getId()));
                    TransactionContextHolder.clearContext();
                } else {
                    // 如果是老的，则恢复原环境
                    TransactionContext context = TransactionContextHolder.getContext();
                    TransactionContextHolder.setContext(context.getOldTransaction());
                }
            }
            if (GatewayContextHolder.getContext() != null) {
                if (GatewayContextHolder.getContext().getOldGateway() == null) {
                    GatewayInfoContextHolder.clearContext();
                    GatewayContextHolder.clearContext();
                } else {
                    // 如果是老的，则恢复原环境
                    GatewayContext context = GatewayContextHolder.getContext();
                    GatewayContextHolder.setContext(context.getOldGateway());
                }
            }
        }
    }

    public TransactionAttributeSource getTransactionAttributeSource() {
        return txAttrSource;
    }

    @Override
    public int getOrder() {
        return DEFAULT_PRIORITY;
    }
}
